Background

Rob Baker et al fit growth parameters to B. rapa growth data and then used those to do function-value-trait QTL mapping.

I used those same parameters and queried a mutual rank (MR) gene expression network to find genes that were in a networks associated with these growth paramters. This was done separately for the CR and UN environments. I looked for overlap between those MR-associated genes and growth parameters.

The goal now is to find the trans eQTL for each of the MR-growth associated genes and ask if the trans eQTL overlap with the growth/function-value QTL. I think I am only going to take the top eQTL for each.

Focused on UN genes only.

This is for the CIM eQTL results

Methodology

For each gene in a MR network with a growth parameter query the eQTL database to find its trans eQTL regions. Then compare overlaps between those and the growth QTL.

Load the libraries and data

Libraries

library(qtl)
package ‘qtl’ was built under R version 3.5.2
library(GenomicRanges)
Loading required package: stats4
Loading required package: BiocGenerics
Loading required package: parallel

Attaching package: ‘BiocGenerics’

The following objects are masked from ‘package:parallel’:

    clusterApply, clusterApplyLB, clusterCall, clusterEvalQ,
    clusterExport, clusterMap, parApply, parCapply, parLapply,
    parLapplyLB, parRapply, parSapply, parSapplyLB

The following objects are masked from ‘package:stats’:

    IQR, mad, sd, var, xtabs

The following objects are masked from ‘package:base’:

    anyDuplicated, append, as.data.frame, basename, cbind,
    colMeans, colnames, colSums, dirname, do.call, duplicated,
    eval, evalq, Filter, Find, get, grep, grepl, intersect,
    is.unsorted, lapply, lengths, Map, mapply, match, mget,
    order, paste, pmax, pmax.int, pmin, pmin.int, Position, rank,
    rbind, Reduce, rowMeans, rownames, rowSums, sapply, setdiff,
    sort, table, tapply, union, unique, unsplit, which,
    which.max, which.min

Loading required package: S4Vectors

Attaching package: ‘S4Vectors’

The following object is masked from ‘package:base’:

    expand.grid

Loading required package: IRanges
Loading required package: GenomeInfoDb
library(tidyverse)
── Attaching packages ───────────────────────────────── tidyverse 1.2.1 ──
✔ ggplot2 3.1.1       ✔ purrr   0.3.2  
✔ tibble  2.1.1       ✔ dplyr   0.8.0.1
✔ tidyr   0.8.3       ✔ stringr 1.4.0  
✔ readr   1.3.1       ✔ forcats 0.4.0  
package ‘ggplot2’ was built under R version 3.5.2package ‘tibble’ was built under R version 3.5.2package ‘tidyr’ was built under R version 3.5.2package ‘purrr’ was built under R version 3.5.2package ‘dplyr’ was built under R version 3.5.2package ‘stringr’ was built under R version 3.5.2package ‘forcats’ was built under R version 3.5.2── Conflicts ──────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::collapse()   masks IRanges::collapse()
✖ dplyr::combine()    masks BiocGenerics::combine()
✖ dplyr::desc()       masks IRanges::desc()
✖ tidyr::expand()     masks S4Vectors::expand()
✖ dplyr::filter()     masks stats::filter()
✖ dplyr::first()      masks S4Vectors::first()
✖ dplyr::lag()        masks stats::lag()
✖ ggplot2::Position() masks BiocGenerics::Position(), base::Position()
✖ purrr::reduce()     masks GenomicRanges::reduce(), IRanges::reduce()
✖ dplyr::rename()     masks S4Vectors::rename()
✖ dplyr::slice()      masks IRanges::slice()
library(magrittr)

Attaching package: ‘magrittr’

The following object is masked from ‘package:purrr’:

    set_names

The following object is masked from ‘package:tidyr’:

    extract

Growth QTL

filepath <- "../input/All2012HeightQTL2.xlsx"
filebase <- filepath %>% basename() %>% str_replace("\\..*$","")
QTLgenes <- readxl::read_excel(filepath)[,-1]

-
/
                                                                        
New names:
* `` -> ...1
QTLgenes <- QTLgenes %>% dplyr::rename(.id=QTL, FVTtrait=FVT) # change names to match previous file
QTLgenes <- QTLgenes %>% filter(str_detect(FVTtrait,"^UN"))
QTLgenes

MR genes from UN

MR_UN_genes <- read_csv("../output/MR_UN_graphs_node_annotation_2012.csv") %>%
  filter(MR_Cutoff <= 50, !duplicated(name)) %>%
  mutate(pos=floor((start+end)/2)) %>%
  select(MR_Cutoff,name, transcript_chrom=chrom, transcript_pos=pos)
Parsed with column specification:
cols(
  .default = col_double(),
  .id = col_character(),
  trt = col_character(),
  name = col_character(),
  chrom = col_character(),
  subject = col_character(),
  AGI = col_character(),
  At_symbol = col_character(),
  At_description = col_character()
)
See spec(...) for full column specifications.
MR_UN_genes

eQTL

load("../output/scanone-MRgene-qtl_2012.RData")
scanone_CIM_UN <- scanone_MR_cim
scanone_CIM_UN <- scanone_CIM_UN[!str_detect(rownames(scanone_CIM_UN),"loc"),] #downstream code cannot deal with interpolated markers.

Find eQTL intervals

UN data

Get the scanone data in a nice format for summarizing and plotting

scanone_UN_gather <- scanone_CIM_UN %>%
  gather(key = gene, value = LOD, -chr, -pos) %>%
  right_join(MR_UN_genes,by=c("gene"="name")) # only keep genes in MR networks

Alternate thredhold.

previously I calcualted the 0.05 threshold of each gene separately and then took the average. What if I calculate the experimenwise threshold?

dim(permtest.cim) 
[1] 1000   56

This represents the 1000 permutations for each gene. So to get an experiment wise threshold I could take the max for each row and then to the 95% of that.

newthreshold <- permtest.cim %>% apply(1,max) %>% quantile(0.95)
newthreshold
     95% 
6.949558 

For MR30, this should only include the MR30 genes

MR30genes <- MR_UN_genes %>% filter(MR_Cutoff <=30) %>% pull(name)
newthresholdMR30 <- permtest.cim[, MR30genes] %>%
  apply(1,max) %>%
  quantile(0.95)
newthresholdMR30
     95% 
6.564742 

plot eQTL peaks…

pl.UN <- scanone_UN_gather %>%
  ggplot(aes(x=pos,y=LOD,color=gene)) +
  geom_line() +
  geom_hline(aes(yintercept=newthreshold),lty=2,lwd=.5,alpha=.5) +
  facet_grid( ~ chr, scales="free") +
  theme(strip.text.y = element_text(angle=0), axis.text.x = element_text(angle=90)) +
  ggtitle("MR gene eQTL")
pl.UN

ggsave(str_c("../output/MR50 gene eQTL UN CIM", Sys.Date(), ".pdf"),width=12,height=8)
ggsave(str_c("../output/MR50 gene eQTL UN CIM", Sys.Date(), ".png"),width=10,height=5)
pl.UN + coord_cartesian(ylim=c(0,10))

plot eQTL peaks for MR30

pl.UN <- scanone_UN_gather %>%
  filter(MR_Cutoff <=30) %>%
  ggplot(aes(x=pos,y=LOD,color=gene)) +
  geom_line() +
  geom_hline(aes(yintercept=newthresholdMR30),lty=2,lwd=.5,alpha=.5) +
  facet_grid( ~ chr, scales="free") +
  theme(strip.text.y = element_text(angle=0), axis.text.x = element_text(angle=90)) +
  ggtitle("MR gene eQTL")
pl.UN

ggsave(str_c("../output/MR30 gene eQTL UN CIM", Sys.Date(), ".pdf"),width=12,height=8)
ggsave(str_c("../output/MR30 gene eQTL UN CIM", Sys.Date(), ".png"),width=10,height=5)

cis and trans plot

scanone_UN_gather %>%
  arrange(transcript_chrom,transcript_pos,pos) %>%
  filter(LOD>newthreshold) %>%
  group_by(gene,chr) %>%
  filter(LOD==max(LOD)) %>%
  ungroup() %>%
  mutate(transcript_index=row_number(),cis_trans=ifelse(chr==transcript_chrom,"cis","trans")) %>%
  ggplot(aes(x=pos,y=transcript_index,shape=cis_trans,color=LOD)) +
  scale_color_gradient(low="magenta1",high="magenta4") +
  geom_point() +
  facet_wrap(~chr,nrow=1) +
  theme_bw() + 
  xlab("QTL position")
ggsave(str_c("../output/MR_gene_eQTL_cistrans_UN_CIM_", Sys.Date(), ".png"),width=10,height = 5)

sig_chromosomes_UN <- scanone_UN_gather %>%
  
  group_by(gene,chr) %>%
  summarize(pos=pos[which.max(LOD)],LOD=max(LOD)) %>%
  filter(LOD > newthreshold)
sig_chromosomes_UN

now for each significant chromosome/trait combo run bayesint

bayesint_list_UN <- apply(sig_chromosomes_UN,1,function(hit) {
  result <- bayesint(scanone_CIM_UN[c("chr","pos",hit["gene"])], 
                     chr=hit["chr"], 
                     lodcolumn = 1,
                     prob=0.99,
                     expandtomarkers = TRUE
  )
  colnames(result)[3] <- "LOD"
  result
})
names(bayesint_list_UN) <- sig_chromosomes_UN$gene
bayesint_list_UN <- lapply(bayesint_list_UN,function(x) x %>% 
                             as.data.frame(stringsAsFactors=FALSE) %>%
                             rownames_to_column(var="markername") %>%
                             mutate(chr=as.character(chr))
)
bayesint_result_UN <- as.tibble(bind_rows(bayesint_list_UN,.id="gene")) %>% 
  select(gene,chr,pos,markername,LOD) %>%
  separate(markername,into=c("chr1","Mbp"),sep="x", convert=TRUE) %>%
  group_by(gene,chr) %>%
  
  
  summarize(eQTL_start_bp=min(Mbp), eQTL_end_bp=max(Mbp), eQTL_start_cM=min(pos), eQTL_end_cM=max(pos), min_eQTL_LOD=min(LOD), max_eQTL_LOD=max(LOD)) %>% 
      
  #for the high QTL peaks the interval width is 0.  That is overly precise and need to widen those.
  mutate(eQTL_start_bp=ifelse(eQTL_start_bp==eQTL_end_bp ,max(0, eQTL_start_bp - 20000), eQTL_start_bp), eQTL_end_bp=ifelse(eQTL_start_bp==eQTL_end_bp, eQTL_end_bp + 20000, eQTL_end_bp))
`as.tibble()` is deprecated, use `as_tibble()` (but mind the new semantics).
This warning is displayed once per session.
  
bayesint_result_UN

annotate UN eQTL

Load annotation

BrapaAnnotation <- read_csv("../input/Brapa_V1.5_annotated.csv")
Missing column names filled in: 'X1' [1]Parsed with column specification:
cols(
  X1 = col_double(),
  name = col_character(),
  chrom = col_character(),
  start = col_double(),
  end = col_double(),
  subject = col_character(),
  AGI = col_character(),
  At_symbol = col_character(),
  At_description = col_character(),
  perc_ID = col_double(),
  aln_length = col_double(),
  mismatch = col_double(),
  gap_open = col_double(),
  qstart = col_double(),
  qend = col_double(),
  sstart = col_double(),
  send = col_double(),
  eval = col_double(),
  score = col_double()
)
BrapaAnnotation
UN_annotated <- lapply(1:nrow(bayesint_result_UN),function(row) {
  qtl <- bayesint_result_UN[row,]
  subset(BrapaAnnotation, chrom==qtl$chr &
           start >= qtl$eQTL_start_bp &
           end <= qtl$eQTL_end_bp) 
}
)
names(UN_annotated) <- bayesint_result_UN$gene
UN_annotated <- bind_rows(UN_annotated,.id="MR_gene") %>%
  left_join(bayesint_result_UN,by=c("MR_gene"="gene","chrom"="chr")) %>% #get eQTL LOD
  left_join(MR_UN_genes,by=c("MR_gene"="name")) %>% # get cutoff
  dplyr::rename(eQTL_candidate=name)
UN_annotated_small <- UN_annotated %>% select(MR_gene,MR_Cutoff,MR_chrom=transcript_chrom,MR_pos=transcript_pos,eQTL_chrom=chrom, eQTL_start_bp, eQTL_end_bp, eQTL_start_cM, eQTL_end_cM, eQTL_candidate,ends_with("LOD"))
UN_annotated_small
write_csv(UN_annotated_small, path=str_c("../output/", 
                                         filebase, 
                                         "_MR_eQTL_UN_ALL_CIM_", 
                                         Sys.Date(), ".csv"))

cis eQTL? Because the BayesInt intervals may be too small, define cis as anything on the same chromosome

UN_annotated_small %>% 
  filter(MR_chrom==eQTL_chrom) %>%
  filter(!duplicated(MR_gene))

total # of MR genes with cis eQTL

#total MR genes with an eQTL at all:
cat("total MR genes with an eQTL:", {
  scanone_UN_gather %>%
  filter(LOD > newthreshold) %>%
  filter(!duplicated(gene)) %>%
  nrow()})
total MR genes with an eQTL: 40
#total MR genes with a cis eQTL
cat("\ntotal MR genes with a cis eQTL:", {
  UN_annotated_small %>% 
  filter(MR_chrom==eQTL_chrom) %>%
  filter(!duplicated(MR_gene)) %>%
  nrow()
})

total MR genes with a cis eQTL: 29

number of genes with a trans eQTL: For simplicity we will consider a single trans eQTL per chromosome

UN_annotated_small %>%
  filter(MR_chrom!=eQTL_chrom) %>%
  group_by(MR_gene, eQTL_chrom) %>%
  filter(!duplicated(MR_gene)) %>%
  ungroup() %>%
  arrange(MR_gene)
UN_annotated_small %>%
  group_by(MR_gene, eQTL_chrom) %>%
  filter(!duplicated(MR_gene)) %>%
  ungroup() %>%
  summarize(cis_eQTL=sum(MR_chrom==eQTL_chrom),
            trans_eQTL=sum(MR_chrom!=eQTL_chrom),
            total_genes=length(unique(MR_gene)))

So, of the 40 genes with eQTL, 29 had cis, 12 had trans, and 1 had both.

given bayesint results, find overlaps with UN growth QTL

UN_MReQTL_QTL_combined <- inner_join(QTLgenes,UN_annotated_small,by=c("name"="eQTL_candidate")) %>%
  select(.id, MR_gene, MR_Cutoff, eQTL_start_bp, eQTL_end_bp, ends_with("LOD"), everything()) %>%
  arrange(.id,desc(max_eQTL_LOD)) %>%
  dplyr::rename(eQTL_candidate=name)
UN_MReQTL_QTL_combined
UN_MReQTL_QTL_combined_small <- UN_MReQTL_QTL_combined %>% filter(!duplicated(eQTL_candidate)) %>%
  select(-MR_gene,-MR_Cutoff) 
UN_MReQTL_QTL_combined_small

number of cis eQTL in overlapping regions

UN_MReQTL_QTL_combined %>% 
  filter(MR_chrom==eQTL_chrom) %>% 
  filter(!duplicated(MR_gene)) %>%
  nrow()
[1] 27

number of trans eQTL in overlapping regions

UN_MReQTL_QTL_combined %>% 
  filter(MR_chrom!=eQTL_chrom) %>% 
  filter(!duplicated(MR_gene)) %>%
  nrow()
[1] 11

Total number of MR genes with an eQTL that overlaps with an FVT

UN_MReQTL_QTL_combined %>% select(MR_gene) %>% unique() %>% nrow()
[1] 37

how many QTL have at least some overlap?

length(unique(QTLgenes$.id))
[1] 16
length(unique(UN_MReQTL_QTL_combined$.id))
[1] 8

8 of 16

write_csv(UN_MReQTL_QTL_combined,
          path=str_c("../output/", filebase, "_MR_eQTL_UN_overlap_CIM_", Sys.Date(), ".csv"))

How to assess if overlap is significant?

I think pull regions of same size as eQTL and ask how often they overlap with growth QTL.

For each eQTL, randomly select a chromosome, then a position, and widen based on interval. Then check overlap. Repeat.

Make table of chromosome info

chr.info <- scanone_CIM_UN %>% 
  as.data.frame() %>%
  rownames_to_column("marker") %>%
  select(marker) %>%
  separate(marker,into=c("chr","bp"),sep="x",convert=TRUE) %>%
  group_by(chr) %>%
  summarize(start=min(bp),end=max(bp))

Make a table of QTL info

qtl.info <- QTLgenes %>%
  group_by(.id) %>%
  summarize(chrom=unique(chrom),start=min(start),end=max(end))
qtl.info
qtl.ranges <- GRanges(seqnames = qtl.info$chrom,ranges=IRanges(start=qtl.info$start,end=qtl.info$end))
qtl.ranges <- GenomicRanges::reduce(qtl.ranges)

The eQTL are in Bayesint_results

sims <- 1000
eQTL.ranges <- GRanges(bayesint_result_UN$chr,
                       ranges = IRanges(start=bayesint_result_UN$eQTL_start_bp,
                                        end=bayesint_result_UN$eQTL_end_bp))
eQTL.ranges <- GenomicRanges::reduce(eQTL.ranges)
set.seed(54321)
sim.results <- sapply(1:sims, function(s) {
  sim.eQTL <- tibble(
    chr=sample(chr.info$chr,
               size = length(eQTL.ranges),
               replace = TRUE,
               prob=chr.info$end/sum(chr.info$end)),
    width=width(eQTL.ranges) # width of the QTL to simulate
  )
  sim.eQTL <- chr.info %>% 
    select(chr,chr.start=start,chr.end=end) %>% right_join(sim.eQTL,by="chr") #need to get the chrom end so we can sample correctly
  sim.eQTL <- sim.eQTL %>% mutate(qtl.start = runif(n=n(),
                                                    min = chr.start,
                                                    max= max(chr.start,chr.end-width)),
                                  qtl.end=qtl.start+width)
  sim.eQTL.ranges <- GRanges(seqnames = sim.eQTL$chr,ranges = IRanges(start=sim.eQTL$qtl.start,end=sim.eQTL$qtl.end))
  suppressWarnings(result <- sum(countOverlaps(qtl.ranges,sim.eQTL.ranges)>0))
  result
})
true.overlap <- sum(countOverlaps(qtl.ranges,eQTL.ranges)) #OK to ignore warnings
Each of the 2 combined objects has sequence levels not in the other:
  - in 'x': A07, A05
  - in 'y': A04, A01
  Make sure to always combine/compare objects based on the same reference
  genome (use suppressWarnings() to suppress this warning).
true.overlap
[1] 8
mean(sim.results >= true.overlap)
[1] 0
tibble(FVTQTL_vs_MReQTL_True_Overlaps=true.overlap,
       N_Simulations_fewer_overlaps=sum(sim.results < true.overlap),
       N_Simulations_greater_equal_overlaps=sum(sim.results >= true.overlap),
       P_value=mean(sim.results >= true.overlap)
) %>%
  write_csv(str_c("../output/", filebase, "_MReQTL_overlap_pval_CIM", Sys.Date(), ".csv"))

significant; 0 of the simulations had as many overlaps as in the true data set.

LS0tCnRpdGxlOiAiTXV0dWFsIFJhbmsgZVFUTCBvdmVybGFwIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBCYWNrZ3JvdW5kCgpSb2IgQmFrZXIgZXQgYWwgZml0IGdyb3d0aCBwYXJhbWV0ZXJzIHRvIEIuIHJhcGEgZ3Jvd3RoIGRhdGEgYW5kIHRoZW4gdXNlZCB0aG9zZSB0byBkbyBmdW5jdGlvbi12YWx1ZS10cmFpdCBRVEwgbWFwcGluZy4KCkkgdXNlZCB0aG9zZSBzYW1lIHBhcmFtZXRlcnMgYW5kIHF1ZXJpZWQgYSBtdXR1YWwgcmFuayAoTVIpIGdlbmUgZXhwcmVzc2lvbiBuZXR3b3JrIHRvIGZpbmQgZ2VuZXMgdGhhdCB3ZXJlIGluIGEgbmV0d29ya3MgYXNzb2NpYXRlZCB3aXRoIHRoZXNlIGdyb3d0aCBwYXJhbXRlcnMuICBUaGlzIHdhcyBkb25lIHNlcGFyYXRlbHkgZm9yIHRoZSBDUiBhbmQgVU4gZW52aXJvbm1lbnRzLiAgSSBsb29rZWQgZm9yIG92ZXJsYXAgYmV0d2VlbiB0aG9zZSBNUi1hc3NvY2lhdGVkIGdlbmVzIGFuZCBncm93dGggcGFyYW1ldGVycy4KClRoZSBnb2FsIG5vdyBpcyB0byBmaW5kIHRoZSB0cmFucyBlUVRMIGZvciBlYWNoIG9mIHRoZSBNUi1ncm93dGggYXNzb2NpYXRlZCBnZW5lcyBhbmQgYXNrIGlmIHRoZSB0cmFucyBlUVRMIG92ZXJsYXAgd2l0aCB0aGUgZ3Jvd3RoL2Z1bmN0aW9uLXZhbHVlIFFUTC4gIEkgdGhpbmsgSSBhbSBvbmx5IGdvaW5nIHRvIHRha2UgdGhlIHRvcCBlUVRMIGZvciBlYWNoLgoKRm9jdXNlZCBvbiBVTiBnZW5lcyBvbmx5LgoKX19UaGlzIGlzIGZvciB0aGUgQ0lNIGVRVEwgcmVzdWx0c19fCgojIyBNZXRob2RvbG9neQoKRm9yIGVhY2ggZ2VuZSBpbiBhIE1SIG5ldHdvcmsgd2l0aCBhIGdyb3d0aCBwYXJhbWV0ZXIgcXVlcnkgdGhlIGVRVEwgZGF0YWJhc2UgdG8gZmluZCBpdHMgdHJhbnMgZVFUTCByZWdpb25zLiAgVGhlbiBjb21wYXJlIG92ZXJsYXBzIGJldHdlZW4gdGhvc2UgYW5kIHRoZSBncm93dGggUVRMLgoKIyMgTG9hZCB0aGUgbGlicmFyaWVzIGFuZCBkYXRhCgpMaWJyYXJpZXMKYGBge3J9CmxpYnJhcnkocXRsKQpsaWJyYXJ5KEdlbm9taWNSYW5nZXMpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KG1hZ3JpdHRyKQpgYGAKCkdyb3d0aCBRVEwKCmBgYHtyfQpmaWxlcGF0aCA8LSAiLi4vaW5wdXQvQWxsMjAxMkhlaWdodFFUTDIueGxzeCIKZmlsZWJhc2UgPC0gZmlsZXBhdGggJT4lIGJhc2VuYW1lKCkgJT4lIHN0cl9yZXBsYWNlKCJcXC4uKiQiLCIiKQoKUVRMZ2VuZXMgPC0gcmVhZHhsOjpyZWFkX2V4Y2VsKGZpbGVwYXRoKVssLTFdClFUTGdlbmVzIDwtIFFUTGdlbmVzICU+JSBkcGx5cjo6cmVuYW1lKC5pZD1RVEwsIEZWVHRyYWl0PUZWVCkgIyBjaGFuZ2UgbmFtZXMgdG8gbWF0Y2ggcHJldmlvdXMgZmlsZQpRVExnZW5lcyA8LSBRVExnZW5lcyAlPiUgZmlsdGVyKHN0cl9kZXRlY3QoRlZUdHJhaXQsIl5VTiIpKQpRVExnZW5lcwpgYGAKCk1SIGdlbmVzIGZyb20gVU4KYGBge3J9Ck1SX1VOX2dlbmVzIDwtIHJlYWRfY3N2KCIuLi9vdXRwdXQvTVJfVU5fZ3JhcGhzX25vZGVfYW5ub3RhdGlvbl8yMDEyLmNzdiIpICU+JQogIGZpbHRlcihNUl9DdXRvZmYgPD0gNTAsICFkdXBsaWNhdGVkKG5hbWUpKSAlPiUKICBtdXRhdGUocG9zPWZsb29yKChzdGFydCtlbmQpLzIpKSAlPiUKICBzZWxlY3QoTVJfQ3V0b2ZmLG5hbWUsIHRyYW5zY3JpcHRfY2hyb209Y2hyb20sIHRyYW5zY3JpcHRfcG9zPXBvcykKTVJfVU5fZ2VuZXMKYGBgCgplUVRMCmBgYHtyfQpsb2FkKCIuLi9vdXRwdXQvc2Nhbm9uZS1NUmdlbmUtcXRsXzIwMTIuUkRhdGEiKQpzY2Fub25lX0NJTV9VTiA8LSBzY2Fub25lX01SX2NpbQpzY2Fub25lX0NJTV9VTiA8LSBzY2Fub25lX0NJTV9VTlshc3RyX2RldGVjdChyb3duYW1lcyhzY2Fub25lX0NJTV9VTiksImxvYyIpLF0gI2Rvd25zdHJlYW0gY29kZSBjYW5ub3QgZGVhbCB3aXRoIGludGVycG9sYXRlZCBtYXJrZXJzLgpgYGAKCiMjIEZpbmQgZVFUTCBpbnRlcnZhbHMKCgojIyMgVU4gZGF0YQoKR2V0IHRoZSBzY2Fub25lIGRhdGEgaW4gYSBuaWNlIGZvcm1hdCBmb3Igc3VtbWFyaXppbmcgYW5kIHBsb3R0aW5nCmBgYHtyfQpzY2Fub25lX1VOX2dhdGhlciA8LSBzY2Fub25lX0NJTV9VTiAlPiUKICBnYXRoZXIoa2V5ID0gZ2VuZSwgdmFsdWUgPSBMT0QsIC1jaHIsIC1wb3MpICU+JQogIHJpZ2h0X2pvaW4oTVJfVU5fZ2VuZXMsYnk9YygiZ2VuZSI9Im5hbWUiKSkgIyBvbmx5IGtlZXAgZ2VuZXMgaW4gTVIgbmV0d29ya3MKYGBgCgojIyMjIEFsdGVybmF0ZSB0aHJlZGhvbGQuCgpwcmV2aW91c2x5IEkgY2FsY3VhbHRlZCB0aGUgMC4wNSB0aHJlc2hvbGQgb2YgZWFjaCBnZW5lIHNlcGFyYXRlbHkgYW5kIHRoZW4gdG9vayB0aGUgYXZlcmFnZS4gIFdoYXQgaWYgSSBjYWxjdWxhdGUgdGhlIGV4cGVyaW1lbndpc2UgdGhyZXNob2xkPwoKYGBge3J9CmRpbShwZXJtdGVzdC5jaW0pIApgYGAKVGhpcyByZXByZXNlbnRzIHRoZSAxMDAwIHBlcm11dGF0aW9ucyBmb3IgZWFjaCBnZW5lLiAgU28gdG8gZ2V0IGFuIGV4cGVyaW1lbnQgd2lzZSB0aHJlc2hvbGQgSSBjb3VsZCB0YWtlIHRoZSBtYXggZm9yIGVhY2ggcm93IGFuZCB0aGVuIHRvIHRoZSA5NSUgb2YgdGhhdC4KCmBgYHtyfQpuZXd0aHJlc2hvbGQgPC0gcGVybXRlc3QuY2ltICU+JSBhcHBseSgxLG1heCkgJT4lIHF1YW50aWxlKDAuOTUpCm5ld3RocmVzaG9sZApgYGAKCkZvciBNUjMwLCB0aGlzIHNob3VsZCBvbmx5IGluY2x1ZGUgdGhlIE1SMzAgZ2VuZXMKCmBgYHtyfQpNUjMwZ2VuZXMgPC0gTVJfVU5fZ2VuZXMgJT4lIGZpbHRlcihNUl9DdXRvZmYgPD0zMCkgJT4lIHB1bGwobmFtZSkKCm5ld3RocmVzaG9sZE1SMzAgPC0gcGVybXRlc3QuY2ltWywgTVIzMGdlbmVzXSAlPiUKICBhcHBseSgxLG1heCkgJT4lCiAgcXVhbnRpbGUoMC45NSkKCm5ld3RocmVzaG9sZE1SMzAKYGBgCgoKCiMjIyMgcGxvdCBlUVRMIHBlYWtzLi4uCmBgYHtyLCBmaWcuaGVpZ2h0PTEwfQpwbC5VTiA8LSBzY2Fub25lX1VOX2dhdGhlciAlPiUKICBnZ3Bsb3QoYWVzKHg9cG9zLHk9TE9ELGNvbG9yPWdlbmUpKSArCiAgZ2VvbV9saW5lKCkgKwogIGdlb21faGxpbmUoYWVzKHlpbnRlcmNlcHQ9bmV3dGhyZXNob2xkKSxsdHk9Mixsd2Q9LjUsYWxwaGE9LjUpICsKICBmYWNldF9ncmlkKCB+IGNociwgc2NhbGVzPSJmcmVlIikgKwogIHRoZW1lKHN0cmlwLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZT0wKSwgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9OTApKSArCiAgZ2d0aXRsZSgiTVIgZ2VuZSBlUVRMIikKcGwuVU4KZ2dzYXZlKHN0cl9jKCIuLi9vdXRwdXQvTVI1MCBnZW5lIGVRVEwgVU4gQ0lNIiwgU3lzLkRhdGUoKSwgIi5wZGYiKSx3aWR0aD0xMixoZWlnaHQ9OCkKZ2dzYXZlKHN0cl9jKCIuLi9vdXRwdXQvTVI1MCBnZW5lIGVRVEwgVU4gQ0lNIiwgU3lzLkRhdGUoKSwgIi5wbmciKSx3aWR0aD0xMCxoZWlnaHQ9NSkKcGwuVU4gKyBjb29yZF9jYXJ0ZXNpYW4oeWxpbT1jKDAsMTApKQpgYGAKCnBsb3QgZVFUTCBwZWFrcyBmb3IgTVIzMApgYGB7ciwgZmlnLmhlaWdodD0xMH0KcGwuVU4gPC0gc2Nhbm9uZV9VTl9nYXRoZXIgJT4lCiAgZmlsdGVyKE1SX0N1dG9mZiA8PTMwKSAlPiUKICBnZ3Bsb3QoYWVzKHg9cG9zLHk9TE9ELGNvbG9yPWdlbmUpKSArCiAgZ2VvbV9saW5lKCkgKwogIGdlb21faGxpbmUoYWVzKHlpbnRlcmNlcHQ9bmV3dGhyZXNob2xkTVIzMCksbHR5PTIsbHdkPS41LGFscGhhPS41KSArCiAgZmFjZXRfZ3JpZCggfiBjaHIsIHNjYWxlcz0iZnJlZSIpICsKICB0aGVtZShzdHJpcC50ZXh0LnkgPSBlbGVtZW50X3RleHQoYW5nbGU9MCksIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTkwKSkgKwogIGdndGl0bGUoIk1SIGdlbmUgZVFUTCIpCnBsLlVOCmdnc2F2ZShzdHJfYygiLi4vb3V0cHV0L01SMzAgZ2VuZSBlUVRMIFVOIENJTSIsIFN5cy5EYXRlKCksICIucGRmIiksd2lkdGg9MTIsaGVpZ2h0PTgpCmdnc2F2ZShzdHJfYygiLi4vb3V0cHV0L01SMzAgZ2VuZSBlUVRMIFVOIENJTSIsIFN5cy5EYXRlKCksICIucG5nIiksd2lkdGg9MTAsaGVpZ2h0PTUpCgpgYGAKCiMjIGNpcyBhbmQgdHJhbnMgcGxvdAoKYGBge3J9CnNjYW5vbmVfVU5fZ2F0aGVyICU+JQogIGFycmFuZ2UodHJhbnNjcmlwdF9jaHJvbSx0cmFuc2NyaXB0X3Bvcyxwb3MpICU+JQogIGZpbHRlcihMT0Q+bmV3dGhyZXNob2xkKSAlPiUKICBncm91cF9ieShnZW5lLGNocikgJT4lCiAgZmlsdGVyKExPRD09bWF4KExPRCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUodHJhbnNjcmlwdF9pbmRleD1yb3dfbnVtYmVyKCksY2lzX3RyYW5zPWlmZWxzZShjaHI9PXRyYW5zY3JpcHRfY2hyb20sImNpcyIsInRyYW5zIikpICU+JQogIGdncGxvdChhZXMoeD1wb3MseT10cmFuc2NyaXB0X2luZGV4LHNoYXBlPWNpc190cmFucyxjb2xvcj1MT0QpKSArCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQobG93PSJtYWdlbnRhMSIsaGlnaD0ibWFnZW50YTQiKSArCiAgZ2VvbV9wb2ludCgpICsKICBmYWNldF93cmFwKH5jaHIsbnJvdz0xKSArCiAgdGhlbWVfYncoKSArIAogIHhsYWIoIlFUTCBwb3NpdGlvbiIpCmdnc2F2ZShzdHJfYygiLi4vb3V0cHV0L01SX2dlbmVfZVFUTF9jaXN0cmFuc19VTl9DSU1fIiwgU3lzLkRhdGUoKSwgIi5wbmciKSx3aWR0aD0xMCxoZWlnaHQgPSA1KQpgYGAKCgoKCgpgYGB7cn0Kc2lnX2Nocm9tb3NvbWVzX1VOIDwtIHNjYW5vbmVfVU5fZ2F0aGVyICU+JQogIAogIGdyb3VwX2J5KGdlbmUsY2hyKSAlPiUKICBzdW1tYXJpemUocG9zPXBvc1t3aGljaC5tYXgoTE9EKV0sTE9EPW1heChMT0QpKSAlPiUKICBmaWx0ZXIoTE9EID4gbmV3dGhyZXNob2xkKQoKc2lnX2Nocm9tb3NvbWVzX1VOCmBgYAoKbm93IGZvciBlYWNoIHNpZ25pZmljYW50IGNocm9tb3NvbWUvdHJhaXQgY29tYm8gcnVuIGJheWVzaW50CgpgYGB7cn0KYmF5ZXNpbnRfbGlzdF9VTiA8LSBhcHBseShzaWdfY2hyb21vc29tZXNfVU4sMSxmdW5jdGlvbihoaXQpIHsKICByZXN1bHQgPC0gYmF5ZXNpbnQoc2Nhbm9uZV9DSU1fVU5bYygiY2hyIiwicG9zIixoaXRbImdlbmUiXSldLCAKICAgICAgICAgICAgICAgICAgICAgY2hyPWhpdFsiY2hyIl0sIAogICAgICAgICAgICAgICAgICAgICBsb2Rjb2x1bW4gPSAxLAogICAgICAgICAgICAgICAgICAgICBwcm9iPTAuOTksCiAgICAgICAgICAgICAgICAgICAgIGV4cGFuZHRvbWFya2VycyA9IFRSVUUKICApCiAgY29sbmFtZXMocmVzdWx0KVszXSA8LSAiTE9EIgogIHJlc3VsdAp9KQpuYW1lcyhiYXllc2ludF9saXN0X1VOKSA8LSBzaWdfY2hyb21vc29tZXNfVU4kZ2VuZQoKYmF5ZXNpbnRfbGlzdF9VTiA8LSBsYXBwbHkoYmF5ZXNpbnRfbGlzdF9VTixmdW5jdGlvbih4KSB4ICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5kYXRhLmZyYW1lKHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvd25hbWVzX3RvX2NvbHVtbih2YXI9Im1hcmtlcm5hbWUiKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGUoY2hyPWFzLmNoYXJhY3RlcihjaHIpKQopCmBgYAoKYGBge3J9CmJheWVzaW50X3Jlc3VsdF9VTiA8LSBhcy50aWJibGUoYmluZF9yb3dzKGJheWVzaW50X2xpc3RfVU4sLmlkPSJnZW5lIikpICU+JSAKICBzZWxlY3QoZ2VuZSxjaHIscG9zLG1hcmtlcm5hbWUsTE9EKSAlPiUKICBzZXBhcmF0ZShtYXJrZXJuYW1lLGludG89YygiY2hyMSIsIk1icCIpLHNlcD0ieCIsIGNvbnZlcnQ9VFJVRSkgJT4lCiAgZ3JvdXBfYnkoZ2VuZSxjaHIpICU+JQogIAogIAogIHN1bW1hcml6ZShlUVRMX3N0YXJ0X2JwPW1pbihNYnApLCBlUVRMX2VuZF9icD1tYXgoTWJwKSwgZVFUTF9zdGFydF9jTT1taW4ocG9zKSwgZVFUTF9lbmRfY009bWF4KHBvcyksIG1pbl9lUVRMX0xPRD1taW4oTE9EKSwgbWF4X2VRVExfTE9EPW1heChMT0QpKSAlPiUgCiAgICAgIAogICNmb3IgdGhlIGhpZ2ggUVRMIHBlYWtzIHRoZSBpbnRlcnZhbCB3aWR0aCBpcyAwLiAgVGhhdCBpcyBvdmVybHkgcHJlY2lzZSBhbmQgbmVlZCB0byB3aWRlbiB0aG9zZS4KICBtdXRhdGUoZVFUTF9zdGFydF9icD1pZmVsc2UoZVFUTF9zdGFydF9icD09ZVFUTF9lbmRfYnAgLG1heCgwLCBlUVRMX3N0YXJ0X2JwIC0gMjAwMDApLCBlUVRMX3N0YXJ0X2JwKSwgZVFUTF9lbmRfYnA9aWZlbHNlKGVRVExfc3RhcnRfYnA9PWVRVExfZW5kX2JwLCBlUVRMX2VuZF9icCArIDIwMDAwLCBlUVRMX2VuZF9icCkpCiAgCgpiYXllc2ludF9yZXN1bHRfVU4KYGBgCgojIyBhbm5vdGF0ZSBVTiBlUVRMCgpMb2FkIGFubm90YXRpb24KYGBge3J9CkJyYXBhQW5ub3RhdGlvbiA8LSByZWFkX2NzdigiLi4vaW5wdXQvQnJhcGFfVjEuNV9hbm5vdGF0ZWQuY3N2IikKQnJhcGFBbm5vdGF0aW9uCmBgYAoKCmBgYHtyfQpVTl9hbm5vdGF0ZWQgPC0gbGFwcGx5KDE6bnJvdyhiYXllc2ludF9yZXN1bHRfVU4pLGZ1bmN0aW9uKHJvdykgewogIHF0bCA8LSBiYXllc2ludF9yZXN1bHRfVU5bcm93LF0KICBzdWJzZXQoQnJhcGFBbm5vdGF0aW9uLCBjaHJvbT09cXRsJGNociAmCiAgICAgICAgICAgc3RhcnQgPj0gcXRsJGVRVExfc3RhcnRfYnAgJgogICAgICAgICAgIGVuZCA8PSBxdGwkZVFUTF9lbmRfYnApIAp9CikKbmFtZXMoVU5fYW5ub3RhdGVkKSA8LSBiYXllc2ludF9yZXN1bHRfVU4kZ2VuZQpVTl9hbm5vdGF0ZWQgPC0gYmluZF9yb3dzKFVOX2Fubm90YXRlZCwuaWQ9Ik1SX2dlbmUiKSAlPiUKICBsZWZ0X2pvaW4oYmF5ZXNpbnRfcmVzdWx0X1VOLGJ5PWMoIk1SX2dlbmUiPSJnZW5lIiwiY2hyb20iPSJjaHIiKSkgJT4lICNnZXQgZVFUTCBMT0QKICBsZWZ0X2pvaW4oTVJfVU5fZ2VuZXMsYnk9YygiTVJfZ2VuZSI9Im5hbWUiKSkgJT4lICMgZ2V0IGN1dG9mZgogIGRwbHlyOjpyZW5hbWUoZVFUTF9jYW5kaWRhdGU9bmFtZSkKCgpVTl9hbm5vdGF0ZWRfc21hbGwgPC0gVU5fYW5ub3RhdGVkICU+JSBzZWxlY3QoTVJfZ2VuZSxNUl9DdXRvZmYsTVJfY2hyb209dHJhbnNjcmlwdF9jaHJvbSxNUl9wb3M9dHJhbnNjcmlwdF9wb3MsZVFUTF9jaHJvbT1jaHJvbSwgZVFUTF9zdGFydF9icCwgZVFUTF9lbmRfYnAsIGVRVExfc3RhcnRfY00sIGVRVExfZW5kX2NNLCBlUVRMX2NhbmRpZGF0ZSxlbmRzX3dpdGgoIkxPRCIpKQoKVU5fYW5ub3RhdGVkX3NtYWxsCgp3cml0ZV9jc3YoVU5fYW5ub3RhdGVkX3NtYWxsLCBwYXRoPXN0cl9jKCIuLi9vdXRwdXQvIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsZWJhc2UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJfTVJfZVFUTF9VTl9BTExfQ0lNXyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFN5cy5EYXRlKCksICIuY3N2IikpCmBgYAoKY2lzIGVRVEw/IEJlY2F1c2UgdGhlIEJheWVzSW50IGludGVydmFscyBtYXkgYmUgdG9vIHNtYWxsLCBkZWZpbmUgY2lzIGFzIGFueXRoaW5nIG9uIHRoZSBzYW1lIGNocm9tb3NvbWUKYGBge3J9ClVOX2Fubm90YXRlZF9zbWFsbCAlPiUgCiAgZmlsdGVyKE1SX2Nocm9tPT1lUVRMX2Nocm9tKSAlPiUKICBmaWx0ZXIoIWR1cGxpY2F0ZWQoTVJfZ2VuZSkpCmBgYAoKdG90YWwgIyBvZiBNUiBnZW5lcyB3aXRoIGNpcyBlUVRMCmBgYHtyfQojdG90YWwgTVIgZ2VuZXMgd2l0aCBhbiBlUVRMIGF0IGFsbDoKY2F0KCJ0b3RhbCBNUiBnZW5lcyB3aXRoIGFuIGVRVEw6IiwgewogIHNjYW5vbmVfVU5fZ2F0aGVyICU+JQogIGZpbHRlcihMT0QgPiBuZXd0aHJlc2hvbGQpICU+JQogIGZpbHRlcighZHVwbGljYXRlZChnZW5lKSkgJT4lCiAgbnJvdygpfSkKCiN0b3RhbCBNUiBnZW5lcyB3aXRoIGEgY2lzIGVRVEwKY2F0KCJcbnRvdGFsIE1SIGdlbmVzIHdpdGggYSBjaXMgZVFUTDoiLCB7CiAgVU5fYW5ub3RhdGVkX3NtYWxsICU+JSAKICBmaWx0ZXIoTVJfY2hyb209PWVRVExfY2hyb20pICU+JQogIGZpbHRlcighZHVwbGljYXRlZChNUl9nZW5lKSkgJT4lCiAgbnJvdygpCn0pCgoKYGBgCgpudW1iZXIgb2YgZ2VuZXMgd2l0aCBhIHRyYW5zIGVRVEw6CkZvciBzaW1wbGljaXR5IHdlIHdpbGwgY29uc2lkZXIgYSBzaW5nbGUgdHJhbnMgZVFUTCBwZXIgY2hyb21vc29tZQpgYGB7cn0KVU5fYW5ub3RhdGVkX3NtYWxsICU+JQogIGZpbHRlcihNUl9jaHJvbSE9ZVFUTF9jaHJvbSkgJT4lCiAgZ3JvdXBfYnkoTVJfZ2VuZSwgZVFUTF9jaHJvbSkgJT4lCiAgZmlsdGVyKCFkdXBsaWNhdGVkKE1SX2dlbmUpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgYXJyYW5nZShNUl9nZW5lKQpgYGAKCmBgYHtyfQpVTl9hbm5vdGF0ZWRfc21hbGwgJT4lCiAgZ3JvdXBfYnkoTVJfZ2VuZSwgZVFUTF9jaHJvbSkgJT4lCiAgZmlsdGVyKCFkdXBsaWNhdGVkKE1SX2dlbmUpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgc3VtbWFyaXplKGNpc19lUVRMPXN1bShNUl9jaHJvbT09ZVFUTF9jaHJvbSksCiAgICAgICAgICAgIHRyYW5zX2VRVEw9c3VtKE1SX2Nocm9tIT1lUVRMX2Nocm9tKSwKICAgICAgICAgICAgdG90YWxfZ2VuZXM9bGVuZ3RoKHVuaXF1ZShNUl9nZW5lKSkpCmBgYAoKU28sIG9mIHRoZSA0MCBnZW5lcyB3aXRoIGVRVEwsIDI5IGhhZCBjaXMsIDEyIGhhZCB0cmFucywgYW5kIDEgaGFkIGJvdGguCgpnaXZlbiBiYXllc2ludCByZXN1bHRzLCBmaW5kIG92ZXJsYXBzIHdpdGggVU4gZ3Jvd3RoIFFUTAoKYGBge3J9ClVOX01SZVFUTF9RVExfY29tYmluZWQgPC0gaW5uZXJfam9pbihRVExnZW5lcyxVTl9hbm5vdGF0ZWRfc21hbGwsYnk9YygibmFtZSI9ImVRVExfY2FuZGlkYXRlIikpICU+JQogIHNlbGVjdCguaWQsIE1SX2dlbmUsIE1SX0N1dG9mZiwgZVFUTF9zdGFydF9icCwgZVFUTF9lbmRfYnAsIGVuZHNfd2l0aCgiTE9EIiksIGV2ZXJ5dGhpbmcoKSkgJT4lCiAgYXJyYW5nZSguaWQsZGVzYyhtYXhfZVFUTF9MT0QpKSAlPiUKICBkcGx5cjo6cmVuYW1lKGVRVExfY2FuZGlkYXRlPW5hbWUpClVOX01SZVFUTF9RVExfY29tYmluZWQKClVOX01SZVFUTF9RVExfY29tYmluZWRfc21hbGwgPC0gVU5fTVJlUVRMX1FUTF9jb21iaW5lZCAlPiUgZmlsdGVyKCFkdXBsaWNhdGVkKGVRVExfY2FuZGlkYXRlKSkgJT4lCiAgc2VsZWN0KC1NUl9nZW5lLC1NUl9DdXRvZmYpIApVTl9NUmVRVExfUVRMX2NvbWJpbmVkX3NtYWxsCmBgYAoKbnVtYmVyIG9mIGNpcyBlUVRMIGluIG92ZXJsYXBwaW5nIHJlZ2lvbnMKYGBge3J9ClVOX01SZVFUTF9RVExfY29tYmluZWQgJT4lIAogIGZpbHRlcihNUl9jaHJvbT09ZVFUTF9jaHJvbSkgJT4lIAogIGZpbHRlcighZHVwbGljYXRlZChNUl9nZW5lKSkgJT4lCiAgbnJvdygpCmBgYAoKbnVtYmVyIG9mIHRyYW5zIGVRVEwgaW4gb3ZlcmxhcHBpbmcgcmVnaW9ucwpgYGB7cn0KVU5fTVJlUVRMX1FUTF9jb21iaW5lZCAlPiUgCiAgZmlsdGVyKE1SX2Nocm9tIT1lUVRMX2Nocm9tKSAlPiUgCiAgZmlsdGVyKCFkdXBsaWNhdGVkKE1SX2dlbmUpKSAlPiUKICBucm93KCkKYGBgCgoKVG90YWwgbnVtYmVyIG9mIE1SIGdlbmVzIHdpdGggYW4gZVFUTCB0aGF0IG92ZXJsYXBzIHdpdGggYW4gRlZUCmBgYHtyfQpVTl9NUmVRVExfUVRMX2NvbWJpbmVkICU+JSBzZWxlY3QoTVJfZ2VuZSkgJT4lIHVuaXF1ZSgpICU+JSBucm93KCkKYGBgCgoKaG93IG1hbnkgUVRMIGhhdmUgYXQgbGVhc3Qgc29tZSBvdmVybGFwPwpgYGB7cn0KbGVuZ3RoKHVuaXF1ZShRVExnZW5lcyQuaWQpKQpsZW5ndGgodW5pcXVlKFVOX01SZVFUTF9RVExfY29tYmluZWQkLmlkKSkKYGBgCgo4IG9mIDE2CgpgYGB7cn0Kd3JpdGVfY3N2KFVOX01SZVFUTF9RVExfY29tYmluZWQsCiAgICAgICAgICBwYXRoPXN0cl9jKCIuLi9vdXRwdXQvIiwgZmlsZWJhc2UsICJfTVJfZVFUTF9VTl9vdmVybGFwX0NJTV8iLCBTeXMuRGF0ZSgpLCAiLmNzdiIpKQpgYGAKCkhvdyB0byBhc3Nlc3MgaWYgb3ZlcmxhcCBpcyBzaWduaWZpY2FudD8KCkkgdGhpbmsgcHVsbCByZWdpb25zIG9mIHNhbWUgc2l6ZSBhcyBlUVRMIGFuZCBhc2sgaG93IG9mdGVuIHRoZXkgb3ZlcmxhcCB3aXRoIGdyb3d0aCBRVEwuCgpGb3IgZWFjaCBlUVRMLCByYW5kb21seSBzZWxlY3QgYSBjaHJvbW9zb21lLCB0aGVuIGEgcG9zaXRpb24sIGFuZCB3aWRlbiBiYXNlZCBvbiBpbnRlcnZhbC4gIFRoZW4gY2hlY2sgb3ZlcmxhcC4gIFJlcGVhdC4KCk1ha2UgdGFibGUgb2YgY2hyb21vc29tZSBpbmZvCmBgYHtyfQpjaHIuaW5mbyA8LSBzY2Fub25lX0NJTV9VTiAlPiUgCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigibWFya2VyIikgJT4lCiAgc2VsZWN0KG1hcmtlcikgJT4lCiAgc2VwYXJhdGUobWFya2VyLGludG89YygiY2hyIiwiYnAiKSxzZXA9IngiLGNvbnZlcnQ9VFJVRSkgJT4lCiAgZ3JvdXBfYnkoY2hyKSAlPiUKICBzdW1tYXJpemUoc3RhcnQ9bWluKGJwKSxlbmQ9bWF4KGJwKSkKYGBgCgpNYWtlIGEgdGFibGUgb2YgUVRMIGluZm8KCmBgYHtyfQpxdGwuaW5mbyA8LSBRVExnZW5lcyAlPiUKICBncm91cF9ieSguaWQpICU+JQogIHN1bW1hcml6ZShjaHJvbT11bmlxdWUoY2hyb20pLHN0YXJ0PW1pbihzdGFydCksZW5kPW1heChlbmQpKQpxdGwuaW5mbwpxdGwucmFuZ2VzIDwtIEdSYW5nZXMoc2VxbmFtZXMgPSBxdGwuaW5mbyRjaHJvbSxyYW5nZXM9SVJhbmdlcyhzdGFydD1xdGwuaW5mbyRzdGFydCxlbmQ9cXRsLmluZm8kZW5kKSkKcXRsLnJhbmdlcyA8LSBHZW5vbWljUmFuZ2VzOjpyZWR1Y2UocXRsLnJhbmdlcykKYGBgCgoKVGhlIGVRVEwgYXJlIGluIEJheWVzaW50X3Jlc3VsdHMKCgpgYGB7cn0Kc2ltcyA8LSAxMDAwCmVRVEwucmFuZ2VzIDwtIEdSYW5nZXMoYmF5ZXNpbnRfcmVzdWx0X1VOJGNociwKICAgICAgICAgICAgICAgICAgICAgICByYW5nZXMgPSBJUmFuZ2VzKHN0YXJ0PWJheWVzaW50X3Jlc3VsdF9VTiRlUVRMX3N0YXJ0X2JwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZW5kPWJheWVzaW50X3Jlc3VsdF9VTiRlUVRMX2VuZF9icCkpCmVRVEwucmFuZ2VzIDwtIEdlbm9taWNSYW5nZXM6OnJlZHVjZShlUVRMLnJhbmdlcykKCnNldC5zZWVkKDU0MzIxKQpzaW0ucmVzdWx0cyA8LSBzYXBwbHkoMTpzaW1zLCBmdW5jdGlvbihzKSB7CiAgc2ltLmVRVEwgPC0gdGliYmxlKAogICAgY2hyPXNhbXBsZShjaHIuaW5mbyRjaHIsCiAgICAgICAgICAgICAgIHNpemUgPSBsZW5ndGgoZVFUTC5yYW5nZXMpLAogICAgICAgICAgICAgICByZXBsYWNlID0gVFJVRSwKICAgICAgICAgICAgICAgcHJvYj1jaHIuaW5mbyRlbmQvc3VtKGNoci5pbmZvJGVuZCkpLAogICAgd2lkdGg9d2lkdGgoZVFUTC5yYW5nZXMpICMgd2lkdGggb2YgdGhlIFFUTCB0byBzaW11bGF0ZQogICkKICBzaW0uZVFUTCA8LSBjaHIuaW5mbyAlPiUgCiAgICBzZWxlY3QoY2hyLGNoci5zdGFydD1zdGFydCxjaHIuZW5kPWVuZCkgJT4lIHJpZ2h0X2pvaW4oc2ltLmVRVEwsYnk9ImNociIpICNuZWVkIHRvIGdldCB0aGUgY2hyb20gZW5kIHNvIHdlIGNhbiBzYW1wbGUgY29ycmVjdGx5CiAgc2ltLmVRVEwgPC0gc2ltLmVRVEwgJT4lIG11dGF0ZShxdGwuc3RhcnQgPSBydW5pZihuPW4oKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbiA9IGNoci5zdGFydCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heD0gbWF4KGNoci5zdGFydCxjaHIuZW5kLXdpZHRoKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdGwuZW5kPXF0bC5zdGFydCt3aWR0aCkKICBzaW0uZVFUTC5yYW5nZXMgPC0gR1JhbmdlcyhzZXFuYW1lcyA9IHNpbS5lUVRMJGNocixyYW5nZXMgPSBJUmFuZ2VzKHN0YXJ0PXNpbS5lUVRMJHF0bC5zdGFydCxlbmQ9c2ltLmVRVEwkcXRsLmVuZCkpCiAgc3VwcHJlc3NXYXJuaW5ncyhyZXN1bHQgPC0gc3VtKGNvdW50T3ZlcmxhcHMocXRsLnJhbmdlcyxzaW0uZVFUTC5yYW5nZXMpPjApKQogIHJlc3VsdAp9KQoKYGBgCgoKYGBge3J9CnRydWUub3ZlcmxhcCA8LSBzdW0oY291bnRPdmVybGFwcyhxdGwucmFuZ2VzLGVRVEwucmFuZ2VzKSkgI09LIHRvIGlnbm9yZSB3YXJuaW5ncwoKdHJ1ZS5vdmVybGFwCgptZWFuKHNpbS5yZXN1bHRzID49IHRydWUub3ZlcmxhcCkKCnRpYmJsZShGVlRRVExfdnNfTVJlUVRMX1RydWVfT3ZlcmxhcHM9dHJ1ZS5vdmVybGFwLAogICAgICAgTl9TaW11bGF0aW9uc19mZXdlcl9vdmVybGFwcz1zdW0oc2ltLnJlc3VsdHMgPCB0cnVlLm92ZXJsYXApLAogICAgICAgTl9TaW11bGF0aW9uc19ncmVhdGVyX2VxdWFsX292ZXJsYXBzPXN1bShzaW0ucmVzdWx0cyA+PSB0cnVlLm92ZXJsYXApLAogICAgICAgUF92YWx1ZT1tZWFuKHNpbS5yZXN1bHRzID49IHRydWUub3ZlcmxhcCkKKSAlPiUKICB3cml0ZV9jc3Yoc3RyX2MoIi4uL291dHB1dC8iLCBmaWxlYmFzZSwgIl9NUmVRVExfb3ZlcmxhcF9wdmFsX0NJTSIsIFN5cy5EYXRlKCksICIuY3N2IikpCmBgYAoKc2lnbmlmaWNhbnQ7IDAgb2YgdGhlIHNpbXVsYXRpb25zIGhhZCBhcyBtYW55IG92ZXJsYXBzIGFzIGluIHRoZSB0cnVlIGRhdGEgc2V0Lgo=